package org.dromara.business.service.impl;

import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.dromara.business.config.Web3jConfig;
import org.dromara.business.domain.*;
import org.dromara.business.domain.vo.TransactionInfoVO;
import org.dromara.business.entity.ContractEvent;
import org.dromara.business.contracts.StakingVaultNative;
import org.springframework.transaction.support.TransactionTemplate;
import org.web3j.protocol.core.methods.response.TransactionReceipt;
import org.dromara.business.mapper.AppUserMapper;
import org.dromara.business.mapper.RebateRecordMapper;
import org.dromara.business.mapper.StakingProductMapper;
import org.dromara.business.request.StakingRequest;
import org.dromara.business.service.BlockchainService;
import org.dromara.business.service.IUserLevelService;
import org.dromara.business.thirdparty.RedisIdGenerator;
import org.dromara.common.core.domain.model.AppLoginUser;
import org.dromara.common.core.domain.model.LoginUser;
import org.dromara.common.core.exception.ServiceException;
import org.dromara.common.core.utils.MapstructUtils;
import org.dromara.common.core.utils.StringUtils;
import org.dromara.common.mybatis.core.page.TableDataInfo;
import org.dromara.common.mybatis.core.page.PageQuery;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.dromara.common.satoken.utils.LoginHelper;
import org.springframework.stereotype.Service;
import org.dromara.business.domain.bo.UserStakingBo;
import org.dromara.business.domain.vo.UserStakingVo;
import org.dromara.business.mapper.UserStakingMapper;
import org.dromara.business.service.IUserStakingService;
import org.springframework.transaction.annotation.Transactional;
import org.web3j.utils.Convert;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.List;
import java.util.Collection;
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.stream.Collectors;

/**
 * 用户质押记录Service业务层处理
 *
 * @author Eric
 * @date 2025-09-05
 */
@Slf4j
@RequiredArgsConstructor
@Service
public class UserStakingServiceImpl implements IUserStakingService {

    private final UserStakingMapper baseMapper;
    private final AppUserMapper appUserMapper;
    private final RebateRecordMapper rebateRecordMapper;
    private final StakingProductMapper stakingProductMapper;
    private final RedisIdGenerator redisIdGenerator;
    private final IUserLevelService userLevelService;
    private final BlockchainService blockchainService;
    private final Web3jConfig web3jConfig;
    private final StakingVaultNative stakingVaultNative;
    private final TransactionTemplate transactionTemplate;

    /**
     * 查询用户质押记录
     *
     * @param id 主键
     * @return 用户质押记录
     */
    @Override
    public UserStakingVo queryById(Long id) {
        return baseMapper.selectVoById(id);
    }

    /**
     * 分页查询用户质押记录列表
     *
     * @param bo        查询条件
     * @param pageQuery 分页参数
     * @return 用户质押记录分页列表
     */
    @Override
    public TableDataInfo<UserStakingVo> queryPageList(UserStakingBo bo, PageQuery pageQuery) {
        Page<UserStakingVo> result = baseMapper.queryPageList(pageQuery.build(), bo);
        return TableDataInfo.build(result);
    }

    /**
     * 查询符合条件的用户质押记录列表
     *
     * @param bo 查询条件
     * @return 用户质押记录列表
     */
    @Override
    public List<UserStakingVo> queryList(UserStakingBo bo) {
        LambdaQueryWrapper<UserStaking> lqw = buildQueryWrapper(bo);
        return baseMapper.selectVoList(lqw);
    }

    private LambdaQueryWrapper<UserStaking> buildQueryWrapper(UserStakingBo bo) {
        LambdaQueryWrapper<UserStaking> lqw = Wrappers.lambdaQuery();
        lqw.orderByAsc(UserStaking::getId);
        lqw.eq(bo.getUserId() != null, UserStaking::getUserId, bo.getUserId());
        lqw.eq(bo.getProductId() != null, UserStaking::getProductId, bo.getProductId());
        lqw.eq(StringUtils.isNotBlank(bo.getOrderNo()), UserStaking::getOrderNo, bo.getOrderNo());
        lqw.eq(bo.getStakingAmount() != null, UserStaking::getStakingAmount, bo.getStakingAmount());
        lqw.eq(bo.getDailyRate() != null, UserStaking::getDailyRate, bo.getDailyRate());
        lqw.eq(bo.getPeriodDays() != null, UserStaking::getPeriodDays, bo.getPeriodDays());
        lqw.eq(bo.getTotalEarnings() != null, UserStaking::getTotalEarnings, bo.getTotalEarnings());
        lqw.eq(bo.getPaidEarnings() != null, UserStaking::getPaidEarnings, bo.getPaidEarnings());
        lqw.eq(bo.getRemainingDays() != null, UserStaking::getRemainingDays, bo.getRemainingDays());
        lqw.eq(bo.getStartDate() != null, UserStaking::getStartDate, bo.getStartDate());
        lqw.eq(bo.getEndDate() != null, UserStaking::getEndDate, bo.getEndDate());
        lqw.eq(bo.getLastRebateDate() != null, UserStaking::getLastRebateDate, bo.getLastRebateDate());
        lqw.eq(bo.getStatus() != null, UserStaking::getStatus, bo.getStatus());
        return lqw;
    }

    /**
     * 新增用户质押记录
     *
     * @param bo 用户质押记录
     * @return 是否新增成功
     */
    @Override
    public Boolean insertByBo(UserStakingBo bo) {
        UserStaking add = MapstructUtils.convert(bo, UserStaking.class);
        validEntityBeforeSave(add);
        add.setCreateTime(LocalDateTime.now());
        boolean flag = baseMapper.insert(add) > 0;
        if (flag) {
            bo.setId(add.getId());
        }
        return flag;
    }

    /**
     * 修改用户质押记录
     *
     * @param bo 用户质押记录
     * @return 是否修改成功
     */
    @Override
    public Boolean updateByBo(UserStakingBo bo) {
        UserStaking update = MapstructUtils.convert(bo, UserStaking.class);
        validEntityBeforeSave(update);
        update.setUpdateTime(LocalDateTime.now());
        return baseMapper.updateById(update) > 0;
    }

    /**
     * 保存前的数据校验
     */
    private void validEntityBeforeSave(UserStaking entity) {
        //TODO 做一些数据校验,如唯一约束
    }

    /**
     * 校验并批量删除用户质押记录信息
     *
     * @param ids     待删除的主键集合
     * @param isValid 是否进行有效性校验
     * @return 是否删除成功
     */
    @Override
    public Boolean deleteWithValidByIds(Collection<Long> ids, Boolean isValid) {
        if (isValid) {
            //TODO 做一些业务上的校验,判断是否需要校验
        }
        return baseMapper.deleteByIds(ids) > 0;
    }

    @Override
    public void processRebates(LocalDateTime dateTime) {
        log.info("开始执行每日返利任务，时间：{}", dateTime);

        // 查询进行中的用户质押数据
        List<UserStaking> activeStakingList = baseMapper.selectList(Wrappers.lambdaQuery(UserStaking.class).eq(UserStaking::getStatus, 1));
        LocalDateTime today = dateTime.truncatedTo(ChronoUnit.DAYS);
        int successCount = 0;
        // 到期总数
        int maturedCount = 0;


        for (UserStaking staking : activeStakingList) {
            try {
//                if (staking.getLastRebateDate() != null && staking.getLastRebateDate().toLocalDate().isEqual(today)) {
//                    log.debug("质押订单{}今日已返利，跳过", staking.getOrderNo());
//                    continue;
//                }
                // 检查今天是否已经发放过收益
                int exists = rebateRecordMapper.checkRewardExists(staking.getId(), 1, today);
                if (exists > 0) {
                    log.debug("质押订单{}今日已返利，跳过", staking.getOrderNo());
                    continue;
                }
                // 检查是否已经开始计息
                if (staking.getStartDate().isAfter(today)) {
                    continue; // 还没开始计息
                }

                long daysPassed = ChronoUnit.DAYS.between(staking.getStartDate().toLocalDate(), today) + 1;

                if (daysPassed > staking.getPeriodDays()) {
                    daysPassed = staking.getPeriodDays();
                }
                // 计算返利金额
                BigDecimal dailyRebate = staking.getStakingAmount().multiply(staking.getDailyRate()).divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);

                // 记录返利奖励
                RebateRecord productReward = new RebateRecord();
                productReward.setTitle("产品返利");
                productReward.setUserId(staking.getUserId());
                productReward.setStakingId(staking.getId());
                productReward.setRebateAmount(dailyRebate);
                productReward.setRebateDate(today);
                productReward.setDayNumber((int) daysPassed);
                productReward.setRewardType(1); // 产品收益
                productReward.setStatus(0);
                productReward.setRemark("每日产品收益");
                productReward.setBaseAmount(staking.getStakingAmount());
                productReward.setRewardRate(staking.getDailyRate());



                staking.setPaidEarnings(staking.getPaidEarnings().add(dailyRebate));
                staking.setRemainingDays(Math.max(0, staking.getPeriodDays() - (int) daysPassed));
                staking.setLastRebateDate(today);

                if (daysPassed >= staking.getPeriodDays()) {
                    staking.setStatus(2);
                    staking.setRemainingDays(0);
                    // 返还用户本金

                    // 扣除用户总质押金额
                    appUserMapper.deductTotalStaked(staking.getUserId(), staking.getStakingAmount());

                    log.info("质押订单{}到期完成，返还本金：{}，总收益：{}", staking.getOrderNo(), staking.getStakingAmount(), staking.getTotalEarnings());
                }

                transactionTemplate.execute(status -> {
                    baseMapper.updateById(staking);
                    // 增加用户总收益
                    appUserMapper.increaseTotalEarnings(staking.getUserId(), dailyRebate);
                    rebateRecordMapper.insert(productReward);
                    return true;
                });


            } catch (Exception e) {
                log.error("处理质押订单{}返利失败", staking.getOrderNo(), e);
                throw new ServiceException("处理质押返利失败");
            }
        }

        List<RebateRecord> rebateRecordList = rebateRecordMapper.selectList(Wrappers.lambdaQuery(RebateRecord.class).eq(RebateRecord::getStatus, 0));

        // 用于保存每个用户收益分发的Map，key为用户钱包地址

        if(!rebateRecordList.isEmpty()){
            Map<String, BigDecimal> userRewardsMap = new HashMap<>();
            for(RebateRecord rebateRecord : rebateRecordList) {
                AppUser currentUser = appUserMapper.selectById(rebateRecord.getUserId());
                if (currentUser != null && currentUser.getWalletAddress() != null) {
                    userRewardsMap.merge(currentUser.getWalletAddress(), rebateRecord.getRebateAmount(), BigDecimal::add);
                }
            }
            // 调用区块链合约释放收益
            if (!userRewardsMap.isEmpty()) {
                try {
                    List<String> walletAddresses = new ArrayList<>(userRewardsMap.keySet());
                    List<BigDecimal> rewardAmounts = new ArrayList<>(userRewardsMap.values());

                    // 转换 BigDecimal 为 Wei (BigInteger)
                    List<java.math.BigInteger> rewardAmountsWei = new ArrayList<>();
                    for (BigDecimal amount : rewardAmounts) {
                        java.math.BigInteger weiAmount = org.web3j.utils.Convert.toWei(amount, org.web3j.utils.Convert.Unit.ETHER).toBigInteger();
                        rewardAmountsWei.add(weiAmount);
                    }

                    // 调用智能合约的 distributeRewards 方法
                    TransactionReceipt receipt = stakingVaultNative.distributeRewards(walletAddresses, rewardAmountsWei).send();

                    if (receipt.isStatusOK()) {
                        log.info("区块链收益分发成功，交易哈希: {}, 分发用户数: {}, 总收益: {}",
                            receipt.getTransactionHash(), walletAddresses.size(),
                            rewardAmounts.stream().reduce(BigDecimal.ZERO, BigDecimal::add));

                        List<Long> ids = rebateRecordList.stream().map(RebateRecord::getId).toList();
                        transactionTemplate.execute(status -> {
                            LambdaUpdateWrapper<RebateRecord> updateWrapper = Wrappers.lambdaUpdate(RebateRecord.class)
                                .set(RebateRecord::getStatus, 1)
                                .in(RebateRecord::getId, ids);
                            rebateRecordMapper.update(updateWrapper);
                            return true;
                        });
                    } else {
                        log.error("区块链收益分发失败，交易哈希: {}", receipt.getTransactionHash());
                        throw new ServiceException("区块链收益分发失败");
                    }
                } catch (Exception e) {
                    log.error("调用区块链合约distributeRewards失败", e);
                    throw new ServiceException("区块链收益分发失败: " + e.getMessage());
                }
            }
        }
        log.info("每日返利任务执行完成");
    }

    @Override
    public void createStaking(StakingRequest request) {
        if(findByTransactionHashAndLogIndex(request.getTransactionHash())){
            return;
        }
        StakingProduct product = stakingProductMapper.selectById(request.getProductId());
//        if (product == null || product.getStatus() != 1) {
//            throw new ServiceException("产品不存在或已下架");
//        }
//        if (request.getAmount().compareTo(product.getMinAmount()) < 0) {
//            throw new ServiceException("质押金额不能小于最小限额：" + product.getMinAmount());
//        }
        TransactionInfoVO vo = blockchainService.getTransactionWithReceipt(request.getTransactionHash());
        String fromAddress = vo.getFromAddress();
        String toAddress = vo.getToAddress();
        String value = vo.getValue();
        BigDecimal eventAmount = Convert.fromWei(new BigDecimal(value), Convert.Unit.ETHER);
        String method = vo.getMethodId();
        if(!method.equals("0xb6b55f25")){
            throw new ServiceException("插入质押数据失败，调用方法不对！");
        }
        AppLoginUser loginUser = LoginHelper.getAppLoginUser();
        AppUser user = appUserMapper.selectById(loginUser.getUserId());
        if(!fromAddress.equals(user.getWalletAddress())){
            throw new ServiceException("插入质押数据失败，调用方法不对！");
        }
        if(!toAddress.equals(web3jConfig.getContracts().getAddresses())){
            throw new ServiceException("插入质押数据失败，调用合约地址不对！");
        }
        if(eventAmount.compareTo(request.getAmount()) != 0){
            throw new ServiceException("插入质押数据失败，金额与链上交易不匹配！");
        }
        appUserMapper.increaseTotalStaked(user.getId(), request.getAmount());

        UserStaking staking = new UserStaking();
        staking.setUserId(user.getId());
        staking.setProductId(request.getProductId());
        staking.setOrderNo(redisIdGenerator.generateOrderNo("S"));
        staking.setStakingAmount(request.getAmount());
        staking.setDailyRate(product.getDailyRate());
        staking.setPeriodDays(product.getPeriodDays());

        BigDecimal totalEarnings = request.getAmount()
            .multiply(product.getDailyRate())
            .multiply(new BigDecimal(product.getPeriodDays()))
            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
        staking.setTotalEarnings(totalEarnings);
        staking.setPaidEarnings(BigDecimal.ZERO);
        staking.setRemainingDays(product.getPeriodDays());

        LocalDateTime now = LocalDateTime.now();
        staking.setStartDate(now.plusDays(1));
        staking.setEndDate(staking.getStartDate().plusDays(product.getPeriodDays()));
        staking.setLastRebateDate(null);
        staking.setStatus(1);

        baseMapper.insert(staking);

        //TODO 更新用户等级

        log.info("用户{}质押产品{}成功，质押金额：{}, 订单号：{}",
            user.getId(), product.getProductName(), request.getAmount(), staking.getOrderNo());

//        BigDecimal dailyEarnings = request.getAmount()
//            .multiply(product.getDailyRate())
//            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
    }

    private AppUser getUserByAddress(String address) {
        return appUserMapper.selectOne(Wrappers.<AppUser>lambdaQuery().eq(AppUser::getWalletAddress, address));
    }

    @Override
    public void createStakingEvent(ContractEvent contractEvent) {
        StakingProduct product = stakingProductMapper.selectById(contractEvent.getProductId());
//        if (product == null || product.getStatus() != 1) {
//            throw new ServiceException("产品不存在或已下架");
//        }
//        if (request.getAmount().compareTo(product.getMinAmount()) < 0) {
//            throw new ServiceException("质押金额不能小于最小限额：" + product.getMinAmount());
//        }

//        AppLoginUser loginUser = LoginHelper.getAppLoginUser();
        AppUser user = getUserByAddress(contractEvent.getWalletAddress());
        appUserMapper.increaseTotalStaked(user.getId(), contractEvent.getAmount());

        UserStaking staking = new UserStaking();
        staking.setUserId(user.getId());
        staking.setProductId(contractEvent.getProductId());
        staking.setOrderNo(redisIdGenerator.generateOrderNo("S"));
        staking.setStakingAmount(contractEvent.getAmount());
        staking.setDailyRate(product.getDailyRate());
        staking.setPeriodDays(product.getPeriodDays());

        BigDecimal totalEarnings = contractEvent.getAmount()
            .multiply(product.getDailyRate())
            .multiply(new BigDecimal(product.getPeriodDays()))
            .divide(new BigDecimal("100"), 2, RoundingMode.HALF_UP);
        staking.setTotalEarnings(totalEarnings);
        staking.setPaidEarnings(BigDecimal.ZERO);
        staking.setRemainingDays(product.getPeriodDays());

        LocalDateTime now = LocalDateTime.now();
        staking.setStartDate(now.plusDays(1));
        staking.setEndDate(staking.getStartDate().plusDays(product.getPeriodDays()));
        staking.setLastRebateDate(null);
        staking.setStatus(1);
        staking.setTxHash(contractEvent.getTransactionHash());
        staking.setLogIndex(contractEvent.getLogIndex());

        baseMapper.insert(staking);

        //TODO 更新用户等级

        log.info("用户{}质押产品{}成功，质押金额：{}, 订单号：{}",
            user.getId(), product.getProductName(), contractEvent.getAmount(), staking.getOrderNo());
    }

    @Override
    public boolean findByTransactionHashAndLogIndex(String transactionHash) {
        return baseMapper.exists(Wrappers.lambdaQuery(UserStaking.class)
            .eq(UserStaking::getTxHash, transactionHash));
    }
}
